﻿namespace FastColoredTextBoxNS
{
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;
    using System.Xml;

    /// <summary>
    /// 	This class records, stores and executes the macros.
    /// </summary>
    public class MacrosManager
    {
        private readonly List<object> macro = new List<object>();

        internal MacrosManager(FastColoredTextBox ctrl)
        {
            this.UnderlayingControl = ctrl;
            this.AllowMacroRecordingByUser = true;
        }

        /// <summary>
        /// 	Allows to user to record macros
        /// </summary>
        public bool AllowMacroRecordingByUser { get; set; }

        private bool isRecording;

        /// <summary>
        /// 	Returns current recording state. Set to True/False to start/stop recording programmatically.
        /// </summary>
        public bool IsRecording
        {
            get
            {
                return this.isRecording;
            }
            set
            {
                this.isRecording = value;
                this.UnderlayingControl.Invalidate();
            }
        }

        /// <summary>
        /// 	FCTB
        /// </summary>
        public FastColoredTextBox UnderlayingControl { get; private set; }

        /// <summary>
        /// 	Executes recorded macro
        /// </summary>
        /// <returns> </returns>
        public void ExecuteMacros()
        {
            this.IsRecording = false;
            this.UnderlayingControl.BeginUpdate();
            this.UnderlayingControl.Selection.BeginUpdate();
            this.UnderlayingControl.BeginAutoUndo();
            foreach (var item in this.macro)
            {
                if (item is Keys)
                {
                    this.UnderlayingControl.ProcessKey((Keys)item);
                }
                if (item is KeyValuePair<char, Keys>)
                {
                    var p = (KeyValuePair<char, Keys>)item;
                    this.UnderlayingControl.ProcessKey(p.Key, p.Value);
                }
            }
            this.UnderlayingControl.EndAutoUndo();
            this.UnderlayingControl.Selection.EndUpdate();
            this.UnderlayingControl.EndUpdate();
        }

        /// <summary>
        /// 	Adds the char to current macro
        /// </summary>
        public void AddCharToMacros(char c, Keys modifiers)
        {
            this.macro.Add(new KeyValuePair<char, Keys>(c, modifiers));
        }

        /// <summary>
        /// 	Adds keyboard key to current macro
        /// </summary>
        public void AddKeyToMacros(Keys keyData)
        {
            this.macro.Add(keyData);
        }

        /// <summary>
        /// 	Clears last recorded macro
        /// </summary>
        public void ClearMacros()
        {
            this.macro.Clear();
        }

        internal void ProcessKey(Keys keyData)
        {
            if (this.IsRecording)
            {
                this.AddKeyToMacros(keyData);
            }
        }

        internal void ProcessKey(char c, Keys modifiers)
        {
            if (this.IsRecording)
            {
                this.AddCharToMacros(c, modifiers);
            }
        }

        /// <summary>
        /// 	Returns True if last macro is empty
        /// </summary>
        public bool MacroIsEmpty
        {
            get
            {
                return this.macro.Count == 0;
            }
        }

        /// <summary>
        /// 	Macros as string.
        /// </summary>
        public string Macros
        {
            get
            {
                CultureInfo cult = Thread.CurrentThread.CurrentUICulture;
                Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
                var kc = new KeysConverter();

                var sb = new StringBuilder();
                sb.AppendLine("<macros>");
                foreach (var item in this.macro)
                {
                    if (item is Keys)
                    {
                        sb.AppendFormat("<item key='{0}' />\r\n", kc.ConvertToString((Keys)item));
                    }
                    else if (item is KeyValuePair<char, Keys>)
                    {
                        var p = (KeyValuePair<char, Keys>)item;
                        sb.AppendFormat("<item char='{0}' key='{1}' />\r\n", (int)p.Key, kc.ConvertToString(p.Value));
                    }
                }
                sb.AppendLine("</macros>");

                Thread.CurrentThread.CurrentUICulture = cult;

                return sb.ToString();
            }

            set
            {
                this.isRecording = false;
                this.ClearMacros();

                if (string.IsNullOrEmpty(value))
                {
                    return;
                }

                var doc = new XmlDocument();
                doc.LoadXml(value);
                XmlNodeList list = doc.SelectNodes("./macros/item");

                CultureInfo cult = Thread.CurrentThread.CurrentUICulture;
                Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
                var kc = new KeysConverter();

                if (list != null)
                {
                    foreach (XmlElement node in list)
                    {
                        XmlAttribute ca = node.GetAttributeNode("char");
                        XmlAttribute ka = node.GetAttributeNode("key");
                        if (ca != null)
                        {
                            if (ka != null)
                            {
                                this.AddCharToMacros((char)int.Parse(ca.Value), (Keys)kc.ConvertFromString(ka.Value));
                            }
                            else
                            {
                                this.AddCharToMacros((char)int.Parse(ca.Value), Keys.None);
                            }
                        }
                        else if (ka != null)
                        {
                            this.AddKeyToMacros((Keys)kc.ConvertFromString(ka.Value));
                        }
                    }
                }

                Thread.CurrentThread.CurrentUICulture = cult;
            }
        }
    }
}